/*
*	Copyright(c) 2020 lutianming email：641471957@qq.com
*
*	Sheeps may be copied only under the terms of the GNU Affero General Public License v3.0
*/

#include "luavms.h"
#include <list>
#include <mutex>
#include "Config.h"

#ifdef __WINDOWS__
#include "io.h"
#else
#include <dirent.h>
#endif // __WINDOWS__

lua_State* globle_L = NULL;

static char path_buf[4096] = { 0x0 };
static size_t path_size = 0;

static std::map<std::string, bool>* GloablLuaVmPackageLoaded = NULL;
static std::map<std::string, bool>* GloablLuaVmGlobalLoaded = NULL;

static std::map<std::string, bool>* TaskLuaVmPackageLoaded = NULL;
static std::map<std::string, bool>* TaskLuaVmGlobalLoaded = NULL;

int sheeps_agent_report(lua_State* L, int status, const char* func, int line) {
	if (status != LUA_OK) {
		const char* msg = lua_tostring(L, -1);
		printf("sheeps: %s:%d %s\n", func, line, msg);
		TaskAgentLog(LOG_ERROR, "sheeps: %s:%d %s", func, line, msg);
		lua_pop(L, 1);  /* remove message */
	}
	return status;
}

int sheeps_report(lua_State* L, int status, LuaProtocol* proto, bool stop, const char* func, int line) {
	if (status != LUA_OK) {
		size_t size = 0;
		const char* msg = lua_tolstring(L, -1, &size);
		if (stop) {
			if (strcmp(msg + (size - sizeof("StopSignal") + 1), "StopSignal") == 0) {
				PlayStop(proto, "强制结束");
				lua_pop(L, 1);  /* remove message */
				return status;
			}
			else
				PlayStop(proto, "%s:%d script error!!!", func, line);
		}
		TaskUserLog(proto, LOG_ERROR, "sheeps: %s:%d %s", func, line, msg);
		lua_pop(L, 1);  /* remove message */
	}
	return status;
}

void stop_signal_hook(lua_State* L, lua_Debug* ar) {
	luaL_error(L, "StopSignal");
}

static size_t getFiles(const char* dir_path, char* buff, size_t buff_size, size_t size)
#ifdef __WINDOWS__
{
	size += snprintf(buff + size, buff_size - size, ";%s/?.lua", dir_path);
	intptr_t hFile = 0;
	struct _finddata_t fileinfo;

	char allfile[256] = { 0x0 };
	snprintf(allfile, sizeof(allfile), "%s/*", dir_path);

	char fullpath[256] = { 0x0 };
	if ((hFile = _findfirst(allfile, &fileinfo)) != -1)
	{
		do
		{
			memset(fullpath, 0, sizeof(fullpath));
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") == 0 || strcmp(fileinfo.name, "..") == 0)
					continue;
				if (isdigit(fileinfo.name[0]) && config_get_string_value("project", fileinfo.name, NULL) != NULL && projectid != atoi(fileinfo.name))
					continue;
				snprintf(fullpath, sizeof(fullpath), "%s/%s", dir_path, fileinfo.name);
				size = getFiles(fullpath, buff, buff_size, size);
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
	return size;
}
#else
{
	DIR* dir;
	struct dirent* ptr;

	if ((dir = opendir(dir_path)) == NULL)
	{
		//perror("Open dir error...");
		return size;
	}
	size += snprintf(buff + size, buff_size - size, ";%s/?.lua", dir_path);

	char fullpath[260] = { 0x0 };
	while ((ptr = readdir(dir)) != NULL)
	{
		memset(fullpath, 0, sizeof(fullpath));
		if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
			continue;
		else if (isdigit(ptr->d_name[0]) && config_get_string_value("project", ptr->d_name, NULL) != NULL && projectid != atoi(ptr->d_name))
			continue;
		else if (ptr->d_type == DT_DIR || ptr->d_type == DT_UNKNOWN)
		{
			snprintf(fullpath, sizeof(fullpath), "%s/%s", dir_path, ptr->d_name);
			size = getFiles(fullpath, buff, buff_size, size);
		}
	}
	closedir(dir);
	return size;
}
#endif // __WINDOWS__

static int lua_State_Set_Package_Path(lua_State* L)
{
	if (path_size == 0) {
		path_size = snprintf(path_buf, sizeof(path_buf), "%s", "./?.lua");
		path_size = getFiles("./project/lua", path_buf, sizeof(path_buf), path_size);
		path_size = getFiles("./liblua", path_buf, sizeof(path_buf), path_size);
	}
	//size = getFiles("./script", buf, sizeof(buf), size);

	lua_getglobal(L, "package");
	lua_pushstring(L, "path");
	lua_pushstring(L, path_buf);
	lua_settable(L, -3);

	lua_pop(L, 1);
	return 1;
}

static void lua_State_Set_Global_Parms(lua_State* L)
{
	lua_pushinteger(L, ACTION_NOTHING);
	lua_setglobal(L, "NOTHING");
	lua_pushinteger(L, ACTION_WAIT);
	lua_setglobal(L, "WAIT");
	lua_pushinteger(L, ACTION_SEND);
	lua_setglobal(L, "SEND");
	lua_pushinteger(L, ACTION_DROP);
	lua_setglobal(L, "DROP");
	lua_pushinteger(L, ACTION_PLAY);
	lua_setglobal(L, "PLAY");
	lua_pushinteger(L, ACTION_PAUSE);
	lua_setglobal(L, "PAUSE");

	lua_pushinteger(L, LOG_TRACE);
	lua_setglobal(L, "LOG_TRACE");
	lua_pushinteger(L, LOG_DEBUG);
	lua_setglobal(L, "LOG_DEBUG");
	lua_pushinteger(L, LOG_NORMAL);
	lua_setglobal(L, "LOG_NORMAL");
	lua_pushinteger(L, LOG_ERROR);
	lua_setglobal(L, "LOG_ERROR");
	lua_pushinteger(L, LOG_FAULT);
	lua_setglobal(L, "LOG_FAULT");
}

int set_global(lua_State* L, LuaProtocol* proto)
{
	lua_newtable(L);
	lua_setfield(L, LUA_REGISTRYINDEX, _FunctionCall);

	lua_pushlightuserdata(L, proto);
	lua_setfield(L, LUA_REGISTRYINDEX, _UserProtoName);

	hClientTaskConfig task = proto->Task; //存储任务信息
	lua_newtable(L);

	lua_pushstring(L, _taskId);
	lua_pushinteger(L, task->taskID);
	lua_settable(L, -3);

	lua_pushstring(L, _runNumber);
	lua_pushinteger(L, task->run_number);
	lua_settable(L, -3);

	lua_pushstring(L, _UserNumber);
	lua_pushinteger(L, proto->UserNumber);
	lua_settable(L, -3);

	lua_pushstring(L, _machineId);
	lua_pushinteger(L, task->machineID);
	lua_settable(L, -3);

	lua_pushstring(L, _projectId);
	lua_pushinteger(L, task->projectID);
	lua_settable(L, -3);

	lua_pushstring(L, _parms);
	lua_newtable(L);
	char* s = task->parms, * e = NULL, * p = NULL;
	bool end = false;
	while (!end)
	{
		e = strchr(s, '&');
		if (!e) end = true;

		p = strchr(s, '=');
		if (!p || (e && p > e) ) {
			if (*s != 0x0 && *s != '&') {
				if (!e) {
					lua_pushstring(L, s);
				}
				else {
					lua_pushlstring(L, s, e - s);
				}
				lua_pushboolean(L, 1);
				lua_settable(L, -3);
			}
		}
		else {
			if (*s != '=') {
				lua_pushlstring(L, s, p - s);
				if (!e) {
					lua_pushstring(L, p + 1);
				}
				else {
					lua_pushlstring(L, p + 1, e - p -1);
				}
				lua_settable(L, -3);
			}
		}
		if (e) {
			s = e + 1;
		}
	}
	lua_settable(L, -3);
	lua_setglobal(L, _Task);
	return 0;
}

lua_State* create_lua_State()
{
	lua_State* L = luaL_newstate();
	if (L == NULL) {
		return NULL;
	}
	// 加载相关库文件
	luaL_openlibs(L);
	register_common_func(L);
	//配置lua运行环境及全局变量
	lua_State_Set_Package_Path(L);
	lua_State_Set_Global_Parms(L);
	//注册C函数api
	register_sheeps_api(L);
	//注册定lua第三方库:如sproto、lpeg
	luaL_open_3rd_lib(L);
	return L;
}

static void global_package_loaded_init(std::map<std::string, bool>* package_loaded){
	package_loaded->clear();
	lua_getglobal(globle_L, "package");
	lua_pushstring(globle_L, "loaded");
	lua_gettable(globle_L, -2);

	lua_pushnil(globle_L);
	while (lua_next(globle_L, -2) != 0) {
		package_loaded->insert(std::pair<std::string, bool>(lua_tostring(globle_L, -2), true));
		lua_pop(globle_L, 1);
	}
	lua_pop(globle_L, lua_gettop(globle_L));
}

static void global_parms_loaded_init(std::map<std::string, bool>* parms_loaded){
	parms_loaded->clear();
	lua_getglobal(globle_L, "_G");

	lua_pushnil(globle_L);
	while (lua_next(globle_L, -2) != 0) {
		parms_loaded->insert(std::pair<std::string, bool>(lua_tostring(globle_L, -2), true));
		lua_pop(globle_L, 1);
	}
	lua_pop(globle_L, lua_gettop(globle_L));
}

static void reset_luavm_package(lua_State* L, std::map<std::string, bool>* package_loade) {
	lua_getglobal(L, "package");
	lua_pushstring(L, "loaded");
	lua_gettable(L, -2);

	lua_pushnil(L);
	while (lua_next(L, -2) != 0) {
		const char* key = lua_tostring(L, -2);
		if (package_loade->find(key) == package_loade->end()) {
			lua_pushstring(L, key);
			lua_pushnil(L);
			lua_settable(L, -5);
		}
		lua_pop(L, 1);
	}
	lua_pop(L, lua_gettop(L));
}

static void reset_luavm_gloable(lua_State* L, std::map<std::string, bool>* parms_loaded) {
	lua_getglobal(L, "_G");

	lua_pushnil(L);
	while (lua_next(L, -2) != 0) {
		const char* key = lua_tostring(L, -2);
		if (parms_loaded->find(key) == parms_loaded->end()) {
			lua_pushstring(L, key);
			lua_pushnil(L);
			lua_settable(L, -5);
		}
		lua_pop(L, 1);
	}
	lua_pop(L, lua_gettop(L));
}

static bool global_luavm_init()
{
	int state = LUA_OK;
	if (!globle_L) { 
		globle_L = create_lua_State(); 
		GloablLuaVmPackageLoaded = GloablLuaVmPackageLoaded ? GloablLuaVmPackageLoaded : new(std::nothrow) std::map<std::string, bool>;
		global_package_loaded_init(GloablLuaVmPackageLoaded);

		GloablLuaVmGlobalLoaded = GloablLuaVmGlobalLoaded ? GloablLuaVmGlobalLoaded : new(std::nothrow) std::map<std::string, bool>;
		global_parms_loaded_init(GloablLuaVmGlobalLoaded);
	}
	else {
		reset_luavm_package(globle_L, GloablLuaVmPackageLoaded);
		reset_luavm_gloable(globle_L, GloablLuaVmGlobalLoaded);
	}
	if (!globle_L) return false;
	// 加载lua文件
	char cmd[64] = { 0x0 };
	snprintf(cmd, sizeof(cmd), "require \"%s\"", _SHEEPL_LUA);
	state = luaL_dostring(globle_L, cmd);
	if (state != LUA_OK) {
		goto error;
	}
	lua_getglobal(globle_L, _GlobalVmInit);
	lua_pushinteger(globle_L, projectid);
	state = lua_pcall(globle_L, 1, 0, 0);
	if (state != LUA_OK){
		goto error;
	}
	lua_getglobal(globle_L, _LuavmInit);
	lua_pushinteger(globle_L, projectid);
	state = lua_pcall(globle_L, 1, 0, 0);
	if (state != LUA_OK){
		goto error;
	}

	TaskLuaVmPackageLoaded = TaskLuaVmPackageLoaded ? TaskLuaVmPackageLoaded : new(std::nothrow) std::map<std::string, bool>;
	global_package_loaded_init(TaskLuaVmPackageLoaded);

	TaskLuaVmGlobalLoaded = TaskLuaVmGlobalLoaded ? TaskLuaVmGlobalLoaded : new(std::nothrow) std::map<std::string, bool>;
	global_parms_loaded_init(TaskLuaVmGlobalLoaded);
	lua_gc(globle_L, LUA_GCCOLLECT, 0);
	return true;
error:
	const char* msg = lua_tostring(globle_L, -1);
	task_agent_stat_msg_set(STATE_READY_ERROR, msg);
	sheeps_agent_report(globle_L, state, __func__, __LINE__);
	//lua_close(globle_L);
	return false;
}

int init_luavms(){
	if (global_luavm_init() == false) return -1;
	return 0;
}

int init_main_script(lua_State* L, LuaProtocol* proto){
	set_global(L, proto);
	int state = luaL_dofile(L, main_lua);
	if (state != LUA_OK){
		sheeps_report(L, state, proto, false, __func__, __LINE__);
		return -1;
	}
	return 0;
}

void reset_luavm(lua_State* L) {
	reset_luavm_package(L, TaskLuaVmPackageLoaded);
	reset_luavm_gloable(L, TaskLuaVmGlobalLoaded);
	lua_gc(L, LUA_GCCOLLECT, 0);
}